﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using ExpressionBuilder;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Diagnostics;
using ExpressionEvaluator;
using System.Linq.Dynamic;

namespace ExpressionEvalPerformanceTest
{
	class Program
	{
		static void Main(string[] args)
		{
			for (int i = 0; i < 10; i++)
			{
				Console.WriteLine("Run number: " + i);
				MeasureNormal(i);
				MeasureExpressionEval(i);
				MeasureDynamicLibrary(i);
				MeasureDeserialization(i);

				MeasureEvalizatorWithExpressionEval(i);
			}

			Console.ReadKey();
		}

		private static void MeasureNormal(int i)
		{
			List<LambdaExpression> lambdas = new List<LambdaExpression>();
			Stopwatch normalWatch = new Stopwatch();
			normalWatch.Start();
			lambdas.Add(GetNormalLambda<Func<int>>(() => i));
			
			lambdas.Add(GetNormalLambda<Func<int>>(() => i));
			lambdas.Add(GetNormalLambda<Func<int, string>>((x) => x.ToString()));

			lambdas.Add(GetNormalLambda<Func<int, int, int>>((x, y) => x * y + 3 + 4));
			lambdas.Add(GetNormalLambda<Func<int, int, int>>((x, y) => (Convert.ToInt32("2") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32("31"))));

			lambdas.Add(GetNormalLambda<Func<int, string>>((x) => x.ToString().ToString()));
			lambdas.Add(GetNormalLambda<Func<int, int, int>>((x, y) => x * y));

			lambdas.Add(GetNormalLambda<Func<int, int, int>>((x, y) => x / y / 2 % 1 + y * x / 2 + 4 / 2 - 6 % x * 3));
			lambdas.Add(GetNormalLambda<Func<string[], int, int>>((x, y) => ((Convert.ToInt32(x[0].ToString()) + Convert.ToInt32(x[1])) + y)));

			lambdas.Add(GetNormalLambda<Func<string>>(() => new DateTime(2009, 1, 1).ToString("d")));
			lambdas.Add(GetNormalLambda<Func<bool>>(() => (5 > 2) || (5 < 2)));
			normalWatch.Stop();
			Console.WriteLine(string.Format("Normal lambda expression creation finished in {0} ms.", normalWatch.ElapsedMilliseconds));
		}

		private static LambdaExpression GetNormalLambda<TDelegate>(Expression<TDelegate> lambda)
		{
			return lambda as LambdaExpression;
		}

		private static void MeasureExpressionEval(int i)
		{
			List<LambdaExpression> lambdas = new List<LambdaExpression>();
			Stopwatch expressionEvalWatch = new Stopwatch();
			expressionEvalWatch.Start();

			lambdas.Add(GetLambdaFromEval<Func<int, int, int>>("(Convert.ToInt32(\"2\") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32(\"31\"))",
				Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")));

			
			lambdas.Add(GetLambdaFromEval<Func<int>>(i.ToString(), null));
			lambdas.Add(GetLambdaFromEval<Func<int, string>>("x.ToString()", 
				Expression.Parameter(typeof(int), "x")));

			lambdas.Add(GetLambdaFromEval<Func<int, int, int>>("x * y + 3 + 4",
				Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")));
			lambdas.Add(GetLambdaFromEval<Func<int, int, int>>("(Convert.ToInt32(\"2\") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32(\"31\"))",
				Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")));

			lambdas.Add(GetLambdaFromEval<Func<int, string>>("x.ToString().ToString()",
				Expression.Parameter(typeof(int), "x")));
			lambdas.Add(GetLambdaFromEval<Func<int, int, int>>("x * y",
				Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")));

			lambdas.Add(GetLambdaFromEval<Func<int, int, int>>("x / y / 2 % 1 + y * x / 2 + 4 / 2 - 6 % x * 3",
				Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")));
			lambdas.Add(GetLambdaFromEval<Func<string[], int, int>>("((Convert.ToInt32(x[0].ToString()) + Convert.ToInt32(x[1])) + y)",
				Expression.Parameter(typeof(string[]), "x"), Expression.Parameter(typeof(int), "y")));

			lambdas.Add(GetLambdaFromEval<Func<string>>("new DateTime(2009, 1, 1).ToString(\"d\")", null));
			lambdas.Add(GetLambdaFromEval<Func<bool>>("(5 > 2) || (5 < 2)", null));
			expressionEvalWatch.Stop();
			Console.WriteLine(string.Format("Expression eval creation finished in {0} ms.", expressionEvalWatch.ElapsedMilliseconds));
		}

		private static void MeasureEvalizatorWithExpressionEval(int i)
		{
			List<LambdaExpression> lambdas = new List<LambdaExpression>();
			Stopwatch expressionEvalizerWatch = new Stopwatch();
			expressionEvalizerWatch.Start();

			lambdas.Add(GetLambdaFromEvalizer<Func<int>>(() => i));
			
			lambdas.Add(GetLambdaFromEvalizer<Func<int>>(() => i));
			lambdas.Add(GetLambdaFromEvalizer<Func<int, string>>((x) => x.ToString()));

			lambdas.Add(GetLambdaFromEvalizer<Func<int, int, int>>((x, y) => x * y + 3 + 4));
			lambdas.Add(GetLambdaFromEvalizer<Func<int, int, int>>((x, y) => (Convert.ToInt32("2") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32("31"))));

			lambdas.Add(GetLambdaFromEvalizer<Func<int, string>>((x) => x.ToString().ToString()));
			lambdas.Add(GetLambdaFromEvalizer<Func<int, int, int>>((x, y) => x * y));

			lambdas.Add(GetLambdaFromEvalizer<Func<int, int, int>>((x, y) => x / y / 2 % 1 + y * x / 2 + 4 / 2 - 6 % x * 3));
			lambdas.Add(GetLambdaFromEvalizer<Func<string[], int, int>>((x, y) => ((Convert.ToInt32(x[0].ToString()) + Convert.ToInt32(x[1])) + y)));

			lambdas.Add(GetLambdaFromEvalizer<Func<string>>(() => new DateTime(2009, 1, 1).ToString("d")));
			lambdas.Add(GetLambdaFromEvalizer<Func<bool>>(() => (5 > 2) || (5 < 2)));
			expressionEvalizerWatch.Stop();
			Console.WriteLine(string.Format("Expression eval through evalizer creation finished in {0} ms.", expressionEvalizerWatch.ElapsedMilliseconds));
		}

		private static void MeasureDynamicLibrary(int i)
		{
			List<LambdaExpression> lambdas = new List<LambdaExpression>();
			Stopwatch expressionEvalWatch = new Stopwatch();
			expressionEvalWatch.Start();

			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y") }, typeof(int), "(Convert.ToInt32(\"2\") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32(\"31\"))",
				null));
			
			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[]{}, typeof(int), i.ToString(), null));
			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[]{Expression.Parameter(typeof(int), "x")}, typeof(string),
				"x.ToString()", null));

			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")}, typeof(int), "x * y + 3 + 4",
				null));
			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y") }, typeof(int), "(Convert.ToInt32(\"2\") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32(\"31\"))",
				null));

			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(int), "x") }, typeof(string), "x.ToString().ToString()",
				null));
			lambdas.Add(GetLambdaFromEval<Func<int, int, int>>("x * y",
				Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y")));

			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y") }, typeof(int), "x / y / 2 % 1 + y * x / 2 + 4 / 2 - 6 % x * 3",
				null));
			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(string[]), "x"), Expression.Parameter(typeof(int), "y") }, typeof(int), "((Convert.ToInt32(x[0].ToString()) + Convert.ToInt32(x[1])) + y)",
				null));

			//lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { Expression.Parameter(typeof(TestClass), "x") }, typeof(bool),
			//    "x.Test(val => val == 1)", null));

			//lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { }, typeof(DateTime), "new System.DateTime(2009, 1, 1)", null));
			lambdas.Add(DynamicExpression.ParseLambda(new ParameterExpression[] { }, typeof(bool), "(5 > 2) || (5 < 2)", null));
			expressionEvalWatch.Stop();
			Console.WriteLine(string.Format("Dynamic Library creation finished in {0} ms.", expressionEvalWatch.ElapsedMilliseconds));
		}

		public class TestClass
		{
			public bool Test(Func<int, bool> test)
			{
				return test(1);
			}
		}

		private static LambdaExpression GetLambdaFromEval<TDelegate>(string code, params ParameterExpression[] parameters)
		{
			var Evaluator = new ExpressionEval(code);
			if (parameters != null)
			{
				foreach (ParameterExpression param in parameters)
				{
					Evaluator.AddParam(param);
				}
			}

			return Evaluator.Eval<TDelegate>();
		}

		private static LambdaExpression GetLambdaFromEvalizer<TDelegate>(Expression<TDelegate> lambda)
		{
			Evalizer evalizer = new Evalizer()
					   .AddLookupNamespace("System.Linq")
					   .AddLookupNamespace("System.Core")
					   .AddLookupNamespace("ExpressionEvalTest");
			string code = evalizer.Evalize(lambda);

			var Evaluator = new ExpressionEval(code);
			foreach (ParameterExpression param in lambda.Parameters)
			{
				Evaluator.AddParam(param);
			}

			return Evaluator.Eval<TDelegate>();
		}

		private static void MeasureDeserialization(int i)
		{
			List<KeyValuePair<Type, string>> serializedLambdas = PrepareDeserializedLambdas(i);
			Stopwatch deserializedWatch = new Stopwatch();
			SerializeLambda<Func<int, int, int>>((x, y) => (Convert.ToInt32("2") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32("31")));
			deserializedWatch.Start();
			foreach (var serializedLambda in serializedLambdas)
			{
				LambdaExpression oneResult = DeserializeLambda(serializedLambda);
			}
			deserializedWatch.Stop();
			Console.WriteLine(string.Format("Deserialization finished in {0} ms.", deserializedWatch.ElapsedMilliseconds));
		}

		private static List<KeyValuePair<Type, string>> PrepareDeserializedLambdas(int i)
		{
			List<KeyValuePair<Type, string>> result = new List<KeyValuePair<Type, string>>();
			result.Add(SerializeLambda<Func<int>>(() => 3));
			result.Add(SerializeLambda<Func<int, string>>((x) => x.ToString()));

			result.Add(SerializeLambda<Func<int, int, int>>((x, y) => x * y + 3 + 4));
			result.Add(SerializeLambda<Func<int, int, int>>((x, y) => (Convert.ToInt32("2") + (y * x)) / ((2 + 4) / 2 - 6 % x * Convert.ToInt32("31"))));

			result.Add(SerializeLambda<Func<int, string>>((x) => x.ToString().ToString()));
			result.Add(SerializeLambda<Func<int, int, int>>((x, y) => x * y));

			result.Add(SerializeLambda<Func<int, int, int>>((x, y) => x / y / 2 % 1 + y * x / 2 + 4 / 2 - 6 % x * 3));
			result.Add(SerializeLambda<Func<string[], int, int>>((x, y) => ((Convert.ToInt32(x[0].ToString()) + Convert.ToInt32(x[1])) + y)));

			result.Add(SerializeLambda<Func<string>>(() => new DateTime(2009, 1, 1).ToString("d")));
			result.Add(SerializeLambda<Func<bool>>(() => (5 > 2) || (5 < 2)));
			return result;
		}



		private static KeyValuePair<Type, string> SerializeLambda<TDelegate>(Expression<TDelegate> lambda)
		{
			EditableExpression mutableLambda = EditableExpression.CreateEditableExpression(lambda);
            MemoryStream ms = new MemoryStream();
            XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, true);
            
            XmlSerializer xs = new XmlSerializer(mutableLambda.GetType());
            xs.Serialize(ms, mutableLambda);

            xdw.Flush();
            ms.Flush();
            return new KeyValuePair<Type,string>(mutableLambda.GetType(), Encoding.UTF8.GetString(ms.ToArray()));
		}
		private static LambdaExpression DeserializeLambda(KeyValuePair<Type, string> lambdaInString)
		{
			XmlSerializer xs = new XmlSerializer(lambdaInString.Key);
			MemoryStream ms2 = new MemoryStream(Encoding.UTF8.GetBytes(lambdaInString.Value));
			//Object o = dcs.ReadObject(ms2);
			object o = xs.Deserialize(ms2);
			if (o is EditableExpression)
			{
				return (o as EditableExpression).ToExpression() as LambdaExpression;
			}
			else
			{
				throw new Exception("Bad deserialization!");
			}
		}
	}
}
